/*!
// https://juejin.cn/post/6934155066198720519
// 超时问题
// https://juejin.cn/post/6844903845282185223
// https://juejin.cn/post/7126043782172114951
// https://segmentfault.com/a/1190000021322450
// 拦截器
// https://juejin.cn/post/7021444877778944037
// https://juejin.cn/post/7126043782172114951
 */

import { encodeQueryString } from "./url"

type FetchRequestErrorType = {
  code: string,
  status: number,
  statusText: string
}


const defaultOptions: Partial<FetchRequestOptionsType> = {
  baseURL: '/',
  // headers: {} as Record<string, any>,
  timeout: 60000
}

type FetchRequestOptionsType = {
  baseURL: string,
  timeout: number,
  beforeSend: (config: Record<string, any>) => void,
  onSuccess: (res: { response: Response, result: any }) => void,
  onError: (reason: FetchRequestErrorType) => void,
  onComplete: (res: { response: Response, result: any }) => void,
}

const defaultRequestParams = {
  baseURL: '/',
  method: 'GET',
  contentType: 'application/json',
  query: null as Record<string, any> | string | null,
  body: null as Record<string, any> | null,
  headers: {} as Record<string, any>,
  timeout: 60000,
  cache: 'no-cache',
  credentials: 'include',
  responseType: 'JSON'
}

type RequestParamsType = typeof defaultRequestParams

export class FetchRequest {
  private readonly options: Partial<FetchRequestOptionsType>

  constructor(options: Partial<FetchRequestOptionsType> = defaultOptions) {
    this.options = options
  }

  async request(url: string, config: Partial<RequestParamsType>) {
    let {
      baseURL,
      method,
      contentType,
      query,
      body,
      headers,
      timeout,
      cache,
      credentials,
      responseType
    } = Object.assign(config, this.options, defaultRequestParams)

    // 处理URL：请求前缀 & 问号参数
    if (query != null) {
      if (typeof query === 'object') {
        url += '?' + encodeQueryString(query)
      } else {
        url += `?${query}`
      }
    }

    // 根据自己的需求来: body传递的是普通对象，我们今天项目需要传递给服务器的是URLENCODED格式，我们才处理它的格式；
    // 如果用户传递的本身就不是普通对象(例如:文件流、字符串、FORM-DATA...)，还是以用户自己写的为主...
    headers['Content-Type'] = contentType
    if (contentType !== 'application/json') {
      if (body != null) {
        // @ts-ignore
        body = encodeQueryString(body)
      }
    }
    // if (typeof body === 'object') {
    //   headers['Content-Type'] = 'application/x-www-form-urlencoded'
    //   body = encodeQueryString(body)
    // } else if (typeof body === 'string') {
    //   try {
    //     // 是JSON字符串
    //     body = JSON.parse(body)
    //     headers['Content-Type'] = 'application/json'
    //   } catch (err) {
    //     // 不是JSON字符串:可以简单粗暴的按照 URL ENCODED 格式字符串处理
    //     headers['Content-Type'] = 'application/x-www-form-urlencoded'
    //   }
    // }
    // 把config配置成为fetch需要的对象
    const requestData: Record<string, any> = {
      method: method.toUpperCase(),
      headers,
      credentials,
      cache
    }

    if (body != null) {
      requestData.body = body
    }

    this.options.beforeSend && this.options.beforeSend(requestData)
    // 发送请求
    let response: Response
    let result: any
    try {
      response = await fetch(baseURL + url, requestData)
      const { status, statusText } = response
      // 只要状态码是以2或者3开始的，才是真正地获取成功
      if (status >= 200 && status < 400) {
        switch (responseType.toUpperCase()) {
          case 'JSON':
            result = response.json()
            break
          case 'TEXT':
            result = response.text()
            break
          case 'BLOB':
            result = response.blob()
            break
          case 'ARRAYBUFFER':
            result = response.arrayBuffer()
            break
        }
        if (this.options.onSuccess) {
          return this.options.onSuccess({ response, result })
        } else {
          return { response, result }
        }
      }

      const errorInfo = {
        code: 'STATUS ERROR',
        status,
        statusText
      }
      if (this.options.onError) {
        return this.options.onError(errorInfo)
      } else {
        return Promise.reject()
      }
    } catch (reason: any) {
      if (this.options.onError) {
        return this.options.onError(reason)
      } else {
        return Promise.reject(reason)
      }
    } finally {
      this.options.onComplete && this.options.onComplete({ response: response!, result })
    }
  }
}
